package com.security.config;

import com.security.filter.TenantFilter;
import com.security.security.config.SmsSecurityConfigurerAdapter;
import com.security.security.handler.CustomAuthenticationHandler;
import com.security.service.LoginUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法权限的设置
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义配置生成过滤器
        http.apply(new SmsSecurityConfigurerAdapter(loginUserDetailsService));
        // ① 添加 认证的类。
//        http.authenticationProvider();
        http.csrf().disable()
                // 新版 -- AuthorizationFilter
//                .authorizeHttpRequests(authorize -> {
//                    authorize
//                            .antMatchers("/login").permitAll()
//                            .anyRequest().authenticated();
//                })
                // 旧版 -- FilterSecurityInterceptor.
                .authorizeRequests(authorize -> {
                    authorize
                            // 认证
                            .anyRequest().authenticated()
                    // 鉴权
//                            .antMatchers("/**")
//                            .access("@rabcService.hasPermission(request,authentication)")
                    ;
                })

                // 第一种
//                .sessionManagement(session -> {
//                    session.sessionConcurrency(concurrency -> {
//                        concurrency
//                                .maximumSessions(1)
//                                .maxSessionsPreventsLogin(true);
//                    });
//                })
                // 第二种
                .sessionManagement(session -> {
                    session.maximumSessions(2); // 禁止同一个用户登录多个session
                    // 不需要进行session认证。
                    session.sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy());
                    // 如果需要禁止同一个用户登录不同的浏览器，最好不要禁用。
//                    session.disable();
//                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                })
                // 记住我功能
                .rememberMe(rememberMe -> {
                    rememberMe.rememberMeServices(persistentTokenBasedRememberMeServices());
                })
//                .httpBasic();
//                .formLogin(Customizer.withDefaults())
                .formLogin(form -> {
                    // authorizeRequests
                    form.loginPage("/login").permitAll();
                    form.successHandler(new CustomAuthenticationHandler());
                    // authorizeHttpRequests
//                    form.loginPage("/login");
                })
                // 登出
                .logout(logout -> {
                    logout.logoutSuccessHandler(logoutSuccessHandler()); // 退出成功之后重定向url
                    logout.invalidateHttpSession(true); // session无效
                    logout.clearAuthentication(true); // 清除认证的用户
                    logout.deleteCookies("RememberMe"); // 清除某个cookie
                })
        // 配置异常的处理方式
//                .exceptionHandling(exception -> {
//                    exception.authenticationEntryPoint();
//                    exception.accessDeniedHandler();
//                })
        ;


        // 在授权之前执行租户过滤器
//        http.addFilterBefore(tenantFilter(), AuthorizationFilter.class)
//                ;
    }

    /**
     * 对web 静态资源进行放行
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/static/**", "/smsLogin.html", "/sms/code");
    }


//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        ② 添加 认证的类。
//        auth.authenticationProvider();
//    }

    /**
     * 配置认证失败的处理方式
     *
     * @return
     */
//    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                System.out.println("认证失败");
            }
        };
    }


    /**
     * 配置授权失败的处理方式
     *
     * @return
     */
//    @Bean
    public AccessDeniedHandler accessDeniedException() {
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                System.out.println("授权失败。。。。");
            }
        };
    }


    /**
     * 配置自定义过滤器
     *
     * @return
     */
//    @Bean
    public TenantFilter tenantFilter() {
        return new TenantFilter();
    }

    /**
     * 配置内存用户
     *
     * @return
     */
//    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        UserDetails admin = User.builder().username("admin")
//                .password("{SHA-256}{pV6dilf5i6PJktYIxXKNgnM4cBi5UlS8gtnV8lILAJk=}14a028f1c32ff54f0e70fd4a8e9344d62d02e265705af97c4e78e3d96b7b1a6a")
                .password("{bcrypt}$2a$10$MaY0kaNfmZ2UNULPwTN1G.SQA17HAWCD/WvK8h/MG5O1yZp9de7gW").roles("ADMIN").build();
        return new InMemoryUserDetailsManager(admin);
    }


    /**
     * 方式一：基于security内置数据库的查询方式
     * 方式二：基于外部数据库的查询方式 --> LoginUserDetailsService
     */
//    @Bean
//    public UserDetailsService jdbcUserDetailsManager() {
//        // security内置系统中的数据库查询。
//        return new JdbcUserDetailsManager();
//    }

    /**
     * 自定义一个UserDetailsService
     */
//    @Bean
    public UserDetailsService customUserDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                return new User("admin", "{noop}admin", null);
            }
        };
    }


    /**
     * 配置所有的加密方式
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return encoder;
    }

    @Autowired
    LoginUserDetailsService loginUserDetailsService;

    /**
     * 记住我 --- 基于cookie实现。
     *
     * @return
     */
//    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("RememberMe", loginUserDetailsService);
        return rememberMeServices;
    }

    @Autowired
    DataSource dataSource;

    /**
     * 记住我 --- 基于数据库实现。
     *
     * @return
     */
    @Bean
    public PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices() {
        // "记住我"的token保存方式
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setJdbcTemplate(jdbcTemplate());
        PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices("RememberMe", loginUserDetailsService, repository);
        return rememberMeServices;
    }

    /**
     * 配置内置数据库（jdbc）的问题
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 退出登录
     *
     * @return
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        SimpleUrlLogoutSuccessHandler handler = new SimpleUrlLogoutSuccessHandler();
        handler.setDefaultTargetUrl("/login");
        return handler;
    }

    public static void main(String[] args) {
//        System.out.println(new BCryptPasswordEncoder().encode("admin"));
        System.out.println(new SCryptPasswordEncoder().encode("user"));
    }
}